package model

import (
	"common/helper"
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	g "github.com/doug-martin/goqu/v9"
	"github.com/meilisearch/meilisearch-go"
	"github.com/valyala/fasthttp"
	"github.com/valyala/fastjson"
	"path/filepath"
	"strings"
	"time"
)

func GameFullTextMeili(page, pageSize int, filter, word string, orderfiled, ordertype string) (GameData_Pid, error) {

	var data GameData_Pid
	offset_t := pageSize * (page - 1)

	key := fmt.Sprintf("%d-games", meta.RedisDBIndex)
	index := meta.Meili.Index(key)
	cond := &meilisearch.SearchRequest{
		Limit:  int64(pageSize),
		Offset: int64(offset_t),
	}

	if len(filter) > 1 {
		cond.Filter = filter
	} else {
		cond.Filter = nil
	}
	if orderfiled == "" {
		orderfiled = "sorting"
	}
	if ordertype == "" {
		ordertype = "desc"
	}
	var Sort = []string{orderfiled + ":" + ordertype}
	cond.Sort = Sort
	searchRes, err := index.Search(
		word,
		cond,
	)
	if err != nil {
		return data, err
	}
	ll := len(searchRes.Hits)
	if ll == 0 {
		data.S = 0
		data.T = 0
		return data, err
	}

	data.D = make([]Game_item, ll)
	data.T = searchRes.EstimatedTotalHits
	data.S = uint(pageSize)

	for i, v := range searchRes.Hits {
		val := v.(map[string]interface{})
		data.D[i].ID = val["id"].(string)
		data.D[i].Pid = val["pid"].(string)
		data.D[i].PlatformID = val["platform_id"].(string)
		data.D[i].Name = val["name"].(string)
		data.D[i].EnName = val["en_name"].(string)
		data.D[i].BrAlias = val["br_alias"].(string)
		data.D[i].ClientType = val["client_type"].(string)
		data.D[i].TagId = val["tag_id"].(string)
		data.D[i].GameType = int(val["game_type"].(float64))
		data.D[i].GameID = val["game_id"].(string)
		data.D[i].Img = val["img"].(string)
		data.D[i].Sorting = int(val["sorting"].(float64))
		data.D[i].Online = int(val["online"].(float64))
		data.D[i].IsHot = int(val["is_hot"].(float64))
		data.D[i].IsFav = int(val["is_fav"].(float64))
		data.D[i].IsNew = int(val["is_new"].(float64))
		data.D[i].HotSort = int(val["hot_sort"].(float64))
		data.D[i].NewSort = int(val["new_sort"].(float64))
		data.D[i].FavSort = int(val["fav_sort"].(float64))
		data.D[i].Maintained = int(val["maintained"].(float64))
	}
	return data, nil
}

func GameBatchUpdate(field string, value int, ids []string) error {

	ex := g.Ex{
		"id": ids,
	}

	recs := g.Record{
		field: fmt.Sprintf("%d", value),
	}
	query, _, _ := dialect.Update("tbl_game_lists").Set(recs).Where(ex).ToSQL()
	_, err := meta.MerchantDB.Exec(query)
	if err != nil {
		return pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}
	GameFlushAll()
	return nil
}

// 更新单条游戏数据
func GameListUpdate(id string, record g.Record) error {

	ex := g.Ex{
		"id": id,
	}
	game := Game_t{}
	query, _, _ := dialect.From("tbl_game_lists").Select(colsGame...).Where(ex).ToSQL()

	err := meta.MerchantDB.Get(&game, query)
	if err != nil && err != sql.ErrNoRows {
		return pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}

	if err == sql.ErrNoRows {
		return errors.New(helper.RecordNotExistErr)
	}

	query, _, _ = dialect.Update("tbl_game_lists").Set(record).Where(ex).ToSQL()

	_, err = meta.MerchantDB.Exec(query)
	if err != nil {
		return pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}

	/*
		ids := []string{id}
		gameFlushMulti(ids)
	*/
	GameFlushAll()
	return nil
}

func GameImgUpload(fctx *fasthttp.RequestCtx) (string, error) {

	fh, err := fctx.FormFile("uploadfile")
	if err != nil {

		return "", errors.New(helper.ParamNull)
	}

	ext := filepath.Ext(fh.Filename)
	if ext != ".webp" {

		return "", errors.New(helper.ParamNull)
	}
	timeout := time.Minute * 10
	//ctx, cancel := context.WithTimeout(ctx, time.Second*20)
	//defer cancel()

	ss, err := session.NewSession(&aws.Config{
		Region:      aws.String("sa-east-1"),
		Credentials: credentials.NewStaticCredentials(meta.MerchantS3.AccessKeyID, meta.MerchantS3.SecretAccessKey, ""),
	})

	sess := session.Must(ss, err)

	// Create a new instance of the service's client with a Session.
	// Optional aws.Config values can also be provided as variadic arguments
	// to the New function. This option allows you to provide service
	// specific configuration.
	svc := s3.New(sess)
	var cancelFn func()
	if timeout > 0 {
		ctx, cancelFn = context.WithTimeout(ctx, timeout)
	}
	if cancelFn != nil {
		defer cancelFn()
	}

	filename := fmt.Sprintf("%d.webp", fctx.Time().UnixMilli())

	f, err := fh.Open()
	if err != nil {

		return "", err
	}
	defer f.Close()

	_, err = svc.PutObjectWithContext(fctx, &s3.PutObjectInput{
		Bucket: aws.String(meta.MerchantS3.Bucket),
		Key:    aws.String(filename),
		Body:   f,
		ACL:    aws.String("public-read"),
	})
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
			// If the SDK can determine the request or retry delay was canceled
			// by a context the CanceledErrorCode error code will be returned.
			//fmt.Fprintf(os.Stderr, "upload canceled due to timeout, %s\n", err.Error())
			return "", fmt.Errorf("upload canceled due to timeout, %s", err.Error())
		} else {
			//fmt.Fprintf(os.Stderr, "failed to upload object, %s\n", err.Error())
			return "", fmt.Errorf("failed to upload object, %s", err.Error())
		}
		//os.Exit(1)
	}

	return filename, nil
}

// 更新单条游戏数据
func GameListUpdateState(id string, online int) error {

	ex := g.Ex{
		"id": id,
	}

	game := Game_t{}
	query, _, _ := dialect.From("tbl_game_lists").Select(colsGame...).Where(ex).ToSQL()

	err := meta.MerchantDB.Get(&game, query)
	if err != nil && err != sql.ErrNoRows {
		return pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}

	if err == sql.ErrNoRows {
		return errors.New(helper.RecordNotExistErr)
	}

	if game.Online == online {
		return errors.New(helper.NoDataUpdate)
	}

	record := g.Record{
		"online": online,
	}
	query, _, _ = dialect.Update("tbl_game_lists").Set(record).Where(ex).ToSQL()

	_, err = meta.MerchantDB.Exec(query)
	if err != nil {
		return pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}

	/*
		ids := []string{id}
		gameFlushMulti(ids)
	*/
	GameFlushAll()
	return nil
}

// 游戏列表 分页查询
func GameList(ex g.Ex, page, pageSize uint, orderby, ordertype, platformId, router string) (GameData_Pid, error) {

	if router == "2" || router == "3" {
		arr := []string{}
		query := ""
		if platformId == "" {
			query = " select distinct game_id from tbl_cfg_fake"
		} else {
			query = " select distinct game_id from tbl_cfg_fake where game=" + platformId
		}

		meta.MerchantDB.Select(&arr, query)
		if len(arr) > 0 {
			if router == "2" {
				ex["a.game_id"] = g.Op{"NOTIN": arr}
			}
			if router == "3" {
				ex["a.game_id"] = g.Op{"IN": arr}
			}
		}

	}

	data := GameData_Pid{}
	if page >= 1 {
		query, _, err := dialect.From(g.T("tbl_game_lists").As("a")).Select(g.COUNT(1)).LeftJoin(g.T("tbl_platforms").As("b"), g.On(g.Ex{"a.platform_id": g.I("b.id")})).Where(ex).ToSQL()
		err = meta.MerchantDB.Get(&data.T, query)
		if err != nil {
			return data, pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
		}
	}
	offset := pageSize * (page - 1)

	orderField := g.L("a.id")
	if orderby != "" {
		orderField = g.L(orderby)
	}

	orderBy := orderField.Desc()
	if strings.ToLower(ordertype) == "asc" {
		orderBy = orderField.Asc()
	}
	if strings.ToLower(ordertype) == "desc" {
		orderBy = orderField.Desc()
	}

	query, _, _ := dialect.From(g.T("tbl_game_lists").As("a")).Select("a.*", "b.pid").
		LeftJoin(g.T("tbl_platforms").As("b"), g.On(g.Ex{"a.platform_id": g.I("b.id")})).Where(ex).Order(orderBy).Offset(offset).Limit(pageSize).ToSQL()

	err := meta.MerchantDB.Select(&data.D, query)
	if err != nil {
		return data, pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}
	data.S = pageSize
	return data, nil
}

func GameFlushAll() {

	var data []Game_item
	query := fmt.Sprintf("select a.*, b.pid from tbl_game_lists as a join tbl_platforms as b " +
		"  on a.platform_id = b.id " +
		"  where a.`online`=1 and b.state=1 and b.pid/10*10 in(SELECT id FROM tbl_platforms where pid=0 and state=1)")
	err := meta.MerchantDB.Select(&data, query)
	if err != nil {
		helper.InfoLog("GameFlushAll：%s", err.Error())
		return
	}

	pipe := meta.MerchantRedis.Pipeline()
	defer pipe.Close()

	for i, v := range data {
		dom, err := fastjson.Parse(v.TagId)
		if err != nil {

			continue
		}
		arr, err := dom.Array()
		if err != nil {

			continue
		}
		for _, val := range arr {
			if val.Exists("id") {
				id := val.GetStringBytes("id")
				data[i].TagIds = append(data[i].TagIds, string(id))
			}
		}

		//data[i].TagId = ""

		b, err := json.Marshal(v)
		if err != nil {

			continue
		}

		key := fmt.Sprintf("g:%s", v.ID)
		pipe.Del(ctx, key)
		pipe.Set(ctx, key, b, 0)
		pipe.Persist(ctx, key)
	}

	pipe.Exec(ctx)

	filterable := []string{
		"pid",
		"is_hot",
		"is_fav",
		"is_new",
		"platform_id",
		"game_type",
		"tag_ids",
		"online",
		"br_alias",
	}
	sortable := []string{
		"sorting",
		"created_at",
		"hot_sort",
		"new_sort",
		"fav_sort",
	}
	searchable := []string{
		"name",
		"en_name",
		"br_alias",
		"platform_id",
		"id",
	}

	b, err := helper.JsonMarshal(data)
	if err != nil {

		return
	}

	key := fmt.Sprintf("%d-games", meta.RedisDBIndex)
	index := meta.Meili.Index(key)
	meta.Meili.DeleteIndex(key)
	index.UpdateFilterableAttributes(&filterable)
	index.UpdateSortableAttributes(&sortable)
	index.UpdateSearchableAttributes(&searchable)

	_, err = index.AddDocuments(b, "id")
	if err != nil {

		return
	}

}

func gameFlushMulti(ids []string) error {

	var data []Game_t

	t := dialect.From("tbl_game_lists")
	query, _, _ := t.Select(colsGame...).Where(g.Ex{"id": ids}).ToSQL()
	err := meta.MerchantDB.Select(&data, query)
	if err != nil {

		return err
	}

	pipe := meta.MerchantRedis.Pipeline()
	defer pipe.Close()

	for i, v := range data {

		dom, err := fastjson.Parse(v.TagId)
		if err != nil {

			continue
		}
		arr, err := dom.Array()
		if err != nil {

			continue
		}
		for _, val := range arr {
			if val.Exists("id") {
				id := val.GetStringBytes("id")
				data[i].TagIds = append(data[i].TagIds, string(id))
			}
		}

		data[i].TagId = ""

		b, err := json.Marshal(v)
		if err != nil {

			continue
		}

		key := fmt.Sprintf("g:%s", v.ID)
		pipe.Del(ctx, key)
		pipe.Set(ctx, key, b, 0)
		pipe.Persist(ctx, key)
	}
	pipe.Exec(ctx)

	b, err := helper.JsonMarshal(data)
	if err != nil {

		return err
	}

	key := fmt.Sprintf("%d-games", meta.RedisDBIndex)
	index := meta.Meili.Index(key)
	index.DeleteDocuments(ids)
	_, err = index.AddDocuments(b, "id")
	if err != nil {

		return err
	}
	return nil
}

// 判断热门游戏数量
func GameHotTotal(id string) (int64, error) {

	ex := g.Ex{
		"id":        g.Op{"neq": id},
		"is_hot":    1,
		"online":    1,
		"game_type": []int{3, 4, 7, 10}, // 小游戏 电子 捕鱼 棋牌
	}
	query, _, _ := dialect.From("tbl_game_lists").Select(g.COUNT(1)).Where(ex).ToSQL()
	//
	var total int64
	err := meta.MerchantDB.Get(&total, query)
	if err != nil {
		return 0, pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}

	return total, nil
}

func GetPgGameByRandom() (int, error) {
	game_id := 0
	query := "select game_id from tbl_game_lists where platform_id=101 and online=1 order by rand()  limit 1"
	err := meta.MerchantDB.Get(&game_id, query)
	if err != nil {
		return 0, pushLog(fmt.Errorf("%s,[%s]", err.Error(), query), "数据库错误")
	}
	return game_id, nil
}
